//
//  vImageProcessor.m
//  vImageStudy
//
//  Created by Evan Xie on 8/7/15.
//  Copyright (c) 2015 Family Wings. All rights reserved.
//

#import "vImageProcessor.h"
#import "ImageProcess.h"


@implementation vImageProcessor


+ (UIImage *)embossEffectForImage:(UIImage *)image
{
    return nil;
}

+ (UIImage *)gaussianBlurEffectForImage1:(UIImage *)inputImage blurRadius:(CGFloat)blurRadius
{
    if (!inputImage) {
        return nil;
    }
    
    CGImageRef inputCGImage = inputImage.CGImage;
    size_t width = CGImageGetWidth(inputCGImage);
    size_t height = CGImageGetHeight(inputCGImage);
    size_t bytesPerRow = CGImageGetBytesPerRow(inputCGImage);
    size_t bitsPerComponent = CGImageGetBitsPerComponent(inputCGImage);
    
    // Create ARGB bitmap context with no image pixel data.
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    CGContextRef bitmapContext = CGBitmapContextCreate(NULL, width, height, bitsPerComponent, bytesPerRow, colorSpace, kCGBitmapByteOrderDefault | kCGImageAlphaPremultipliedFirst);
    CGColorSpaceRelease(colorSpace);
    if (!bitmapContext) {
        NSLog(@"Create bitmap context failed.");
        return nil;
    }
    
    // Draw input image to bitmap context, then we can get the image pixel data.
    CGContextDrawImage(bitmapContext, (CGRect){.origin.x = 0.0f, .origin.y = 0.0f, .size.width = width, .size.height = height}, inputCGImage);
    
    // Get the image pixel data from bitmap context
    void* data = CGBitmapContextGetData(bitmapContext);
    
    // Create output buffer to hold the image pixel data which will be processed later.
    const size_t pixelCount = bytesPerRow * height;
    void *outPixelBuffer = malloc(pixelCount);
    vImage_Buffer inBuffer = {data, height, width, bytesPerRow};
    vImage_Buffer outBuffer = {outPixelBuffer, height, width, bytesPerRow};
    
    // A description of how to compute the box kernel width from the Gaussian
    // radius (aka standard deviation) appears in the SVG spec:
    // http://www.w3.org/TR/SVG/filters.html#feGaussianBlurElement
    //
    // For larger values of 's' (s >= 2.0), an approximation can be used: Three
    // successive box-blurs build a piece-wise quadratic convolution kernel, which
    // approximates the Gaussian kernel to within roughly 3%.
    //
    // let d = floor(s * 3*sqrt(2*pi)/4 + 0.5)
    //
    // ... if d is odd, use three box-blurs of size 'd', centered on the output pixel.
    //
    
    CGFloat inputRadius = blurRadius * inputImage.scale;
    uint32_t radius = floor((inputRadius * 3. * sqrt(2 * M_PI) / 4 + 0.5) / 2);
    
    radius |= 1; // Force radius to be odd so that the three box-blur methodology works.
    
    NSInteger tempBufferSize = vImageBoxConvolve_ARGB8888(&inBuffer, &outBuffer, NULL, 0, 0, radius, radius, NULL, kvImageGetTempBufferSize | kvImageEdgeExtend);
    void *tempBuffer = malloc(tempBufferSize);
    
    vImageBoxConvolve_ARGB8888(&inBuffer, &outBuffer, tempBuffer, 0, 0, radius, radius, NULL, kvImageEdgeExtend);
    vImageBoxConvolve_ARGB8888(&outBuffer, &inBuffer, tempBuffer, 0, 0, radius, radius, NULL, kvImageEdgeExtend);
    vImageBoxConvolve_ARGB8888(&inBuffer, &outBuffer, tempBuffer, 0, 0, radius, radius, NULL, kvImageEdgeExtend);
    
    free(tempBuffer);
    
    memcpy(data, outPixelBuffer, pixelCount);
    free(outPixelBuffer);
    
    CGImageRef outputCGImage = CGBitmapContextCreateImage(bitmapContext);
    UIImage* outputImage = [UIImage imageWithCGImage:outputCGImage];
    
    CGImageRelease(outputCGImage);
    CGContextRelease(bitmapContext);
    
    return outputImage;
}

+ (UIImage *)gaussianBlurEffectForImage:(UIImage *)inputImage blurRadius:(CGFloat)blurRadius
{
    if (!inputImage) {
        return nil;
    }
    
    CGImageRef inputCGImage = inputImage.CGImage;
    
    vImage_Buffer inBuffer;
    vImage_Buffer outBuffer;
    
    vImage_CGImageFormat imageFormat = {
        .bitsPerComponent = 8,
        .bitsPerPixel = 32,
        .colorSpace = NULL,
        .bitmapInfo = kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrderDefault,
        .version = 0,
        .decode = NULL,
        .renderingIntent = kCGRenderingIntentDefault
    };

    // Get image piexel data from original image.
    vImage_Error error = vImageBuffer_InitWithCGImage(&inBuffer, &imageFormat, NULL, inputCGImage, kvImagePrintDiagnosticsToConsole);
    if (error != kvImageNoError) {
        NSLog(@"[ERROR] vImageBuffer_InitWithCGImage failed with an error code: %zi", error);
        return nil;
    }
    
    error = vImageBuffer_Init(&outBuffer, inBuffer.height, inBuffer.width, imageFormat.bitsPerPixel, kvImageNoFlags);
    if (error != kvImageNoError) {
        NSLog(@"[ERROR] vImageBuffer_Init failed with an error code: %zi", error);
        free(inBuffer.data);
        return nil;
    }
    
    // A description of how to compute the box kernel width from the Gaussian
    // radius (aka standard deviation) appears in the SVG spec:
    // http://www.w3.org/TR/SVG/filters.html#feGaussianBlurElement
    //
    // For larger values of 's' (s >= 2.0), an approximation can be used: Three
    // successive box-blurs build a piece-wise quadratic convolution kernel, which
    // approximates the Gaussian kernel to within roughly 3%.
    //
    // let d = floor(s * 3*sqrt(2*pi)/4 + 0.5)
    //
    // ... if d is odd, use three box-blurs of size 'd', centered on the output pixel.
    //
    CGFloat inputRadius = blurRadius * inputImage.scale;
    uint32_t radius = floor((inputRadius * 3. * sqrt(2 * M_PI) / 4 + 0.5) / 2);
    
    radius |= 1; // Force radius to be odd so that the three box-blur methodology works.
    
    NSInteger tempBufferSize = vImageBoxConvolve_ARGB8888(&inBuffer, &outBuffer, NULL, 0, 0, radius, radius, NULL, kvImageGetTempBufferSize | kvImageEdgeExtend);
    void *tempBuffer = malloc(tempBufferSize);
    
    vImageBoxConvolve_ARGB8888(&inBuffer, &outBuffer, tempBuffer, 0, 0, radius, radius, NULL, kvImageEdgeExtend);
    vImageBoxConvolve_ARGB8888(&outBuffer, &inBuffer, tempBuffer, 0, 0, radius, radius, NULL, kvImageEdgeExtend);
    vImageBoxConvolve_ARGB8888(&inBuffer, &outBuffer, tempBuffer, 0, 0, radius, radius, NULL, kvImageEdgeExtend);
    
    free(tempBuffer);
    
    UIImage *outputImage = nil;
    CGImageRef outputCGImage = vImageCreateCGImageFromBuffer(&outBuffer, &imageFormat, NULL, NULL, kvImageNoFlags, NULL);
    if (outputCGImage == NULL) {
        NSLog(@"[ERROR] Create output CGImageRef failed.");
        goto MethodEnd;
    }
    
    outputImage = [UIImage imageWithCGImage:outputCGImage];
    
MethodEnd:
    
    if (outputCGImage) {
        CGImageRelease(outputCGImage);
    }
    
    free(inBuffer.data);
    free(outBuffer.data);
    
    return outputImage;
}

@end
